package com.aiwiown.snackmq.cluster.config;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 集群层专用的、类型安全的配置类。
 * 这个类的目的是将 Broker 层的配置与 Cluster 层的配置解耦。
 * Broker 层负责从配置文件加载所有信息，然后创建一个 ClusterConfig 实例，
 * 仅包含集群模块运行所需的参数，并将其传递给集群模块的组件（如 MetadataManager）。
 *
 * 这样做的好处：
 * 1. 边界清晰：明确了 broker -> cluster 的依赖方向和数据流。
 * 2. 关注点分离：Cluster 模块不需要知道 BrokerConfig 的全部内容，只关心自己需要的配置。
 * 3. 类型安全：避免了在模块间传递通用的 Map<String, Object> 或 Object，减少了运行时错误。
 * 4. 易于测试：可以轻松地为 Cluster 模块的单元测试创建独立的 ClusterConfig 对象。
 */
@Data
@Slf4j
public class ClusterConfig {

    /**
     * 当前节点的主机名或 IP 地址。
     */
    private String host;

    /**
     * 当前节点用于 Raft 内部通信的端口。
     */
    private int raftPort;

    /**
     * 当前节点对外提供业务服务的端口。
     * 客户端将连接到这个端口。
     */
    private int businessPort;

    /**
     * Raft 集群的初始节点配置列表。
     * 格式为 "host1:port1,host2:port2,..."
     */
    private String raftInitialConf;

    /**
     * Raft 选举超时时间（毫秒）。
     * Follower 在此时间内未收到 Leader 心跳，将发起新一轮选举。
     */
    private int raftElectionTimeoutMs = 1000; // 默认 1 秒

    /**
     * 【核心修复】新增字段，用于存储整个集群的 Raft 地址到业务端口的映射。
     * Key: Raft 地址 (e.g., "localhost:9877")
     * Value: 业务端口 (e.g., 9876)
     * 这个 Map 是解决地址解析问题的关键，它提供了明确、无歧义的地址转换方式。
     */
    private Map<String, Integer> raftAddressToBusinessPortMap = new HashMap<>();

    /**
     * 【新增】一个静态工厂方法，用于从一个通用的 BrokerConfig 对象（通过反射）创建 ClusterConfig。
     * 这是一个向后兼容和解耦的关键适配器。它允许 Broker 层使用自己的配置对象，
     * 同时为 Cluster 层提供一个干净、专用的配置实例。
     *
     * @param brokerConfigObject 一个代表 Broker 配置的通用对象。
     * @return 一个填充了相应值的 ClusterConfig 实例。
     */
    public static ClusterConfig fromBrokerConfig(Object brokerConfigObject) {
        ClusterConfig clusterConfig = new ClusterConfig();
        if (brokerConfigObject == null) {
            log.warn("brokerConfigObject is null, returning default ClusterConfig.");
            return clusterConfig;
        }

        // 使用反射安全地提取值，避免在 cluster 模块中直接依赖 BrokerConfig 类。
        try {
            clusterConfig.setHost((String) getProperty(brokerConfigObject, "getHost"));
            clusterConfig.setRaftPort((Integer) getProperty(brokerConfigObject, "getRaftPort"));
            clusterConfig.setBusinessPort((Integer) getProperty(brokerConfigObject, "getBusinessPort"));
            clusterConfig.setRaftInitialConf((String) getProperty(brokerConfigObject, "getRaftInitialConf"));
            clusterConfig.setRaftElectionTimeoutMs((Integer) getProperty(brokerConfigObject, "getRaftElectionTimeoutMs"));

            // 【重要】基于初始配置构建地址映射
            String initialConf = clusterConfig.getRaftInitialConf();
            if (initialConf != null && !initialConf.isEmpty()) {
                Map<String, Integer> addressMap = buildAddressMapFromInitialConf(initialConf);
                clusterConfig.setRaftAddressToBusinessPortMap(addressMap);
                log.info("Created address map from BrokerConfig (reflection): {}", addressMap);
            }

        } catch (Exception e) {
            log.error("Failed to create ClusterConfig from broker config object via reflection.", e);
        }
        return clusterConfig;
    }

    /**
     * 反射辅助方法，用于安全地获取属性值。
     */
    private static Object getProperty(Object obj, String methodName) {
        try {
            Method method = obj.getClass().getMethod(methodName);
            return method.invoke(obj);
        } catch (Exception e) {
            // 在日志中记录，但允许方法继续执行，以便使用默认值
            log.trace("Could not get property using method {} from object {}", methodName, obj.getClass().getSimpleName(), e);
            return null;
        }
    }

    /**
     * 从初始配置字符串构建 Raft 地址到业务端口的映射
     * 使用 raftPort - 1 作为业务端口的约定
     *
     * @param initialConf 初始配置字符串
     * @return 构建好的地址映射
     */
    private static Map<String, Integer> buildAddressMapFromInitialConf(String initialConf) {
        Map<String, Integer> addressMap = new HashMap<>();
        for (String peerStr : initialConf.split(",")) {
            String[] parts = peerStr.trim().split(":");
            if (parts.length == 2) {
                try {
                    int raftPort = Integer.parseInt(parts[1]);
                    // 使用 raftPort - 1 作为业务端口，这是旧约定
                    int businessPort = raftPort - 1;
                    // 保存原始地址格式（带端口）到业务端口的映射
                    addressMap.put(peerStr, businessPort);
                } catch (NumberFormatException e) {
                    log.error("Invalid port in raftInitialConf peer string: {}", peerStr, e);
                }
            } else {
                log.warn("Invalid peer string format in raftInitialConf: {}", peerStr);
            }
        }
        return addressMap;
    }

    /**
     * 【新增】一个辅助方法，用于手动添加 Raft 地址到业务端口的映射。
     * 这在某些复杂的部署环境中可能很有用。
     *
     * @param raftAddress Raft 地址 (e.g., "localhost:9877")
     * @param businessPort 业务端口 (e.g., 9876)
     */
    public void addRaftAddressMapping(String raftAddress, int businessPort) {
        this.raftAddressToBusinessPortMap.put(raftAddress, businessPort);
    }
}